package org.icosagon.core.math;

import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.icosagon.core.dice.Die;
import org.matheclipse.parser.client.eval.DoubleEvaluator;
import org.icosagon.core.character.Character;

/**
 * This class is used to read an equation string and auto generate
 * the values for the dice variables. This then allows the equation to
 * be properly parsed and calculated by a math parser.<br />
 * <b>EX:</b> "2d6+3" => "10+3" or "4+3"<br />
 * You may also set this equation to "inf" for infinity or infinate value.
 * @author Daniel Owens
 */
public class Equation {
    private String eval = "";
    private Character compare = null;

    public Equation() {}
    public Equation(String value) {
        this.setEquation(value);
    }

    /**
     * Sets the equation to have the dice rolls calcuated on.
     * @param equation
     */
    public void setEquation(String equation) {
        if(equation == null)
            this.eval = "";
        else
            this.eval = equation;
    }

    /**
     * Sets the specified <code>Character</code> to pull the
     * variable values from.
     * @param character
     */
    public void setCharacter(Character character) {
        this.compare = character;
    }

    public Character getCharacter() {
        return this.compare;
    }

    /**
     * Returns the current equation to be evaluated by this object.
     * @return The equation before the dice rolls are calculated.
     */
    public String getEquation() {
        return this.eval;
    }

    /**
     * This will return a string with all of the variables defined.
     * If a varible does not have an assigned value then the
     * value 0 is auto-assigned to it.<br />
     * Dice rolls are not generated in the result.
     * @return
     */
    public String getDefinedEquation() {
        return this.assignVariables(this.eval);
    }

    /**
     * This method will return an equation with all of the variables
     * defined and all of the dice rolls generated.
     * @return
     */
    public String getSolvableEquation() {
        if(this.eval.equals("inf"))
            return this.eval;
        String calc = this.assignVariables(this.eval);
        calc = this.rollDice(calc);
        return calc;
    }

    /**
     * Returns all of the variables in this equation.
     * @return
     */
    public Vector<String> getVariables() {
        Vector<String> vars = new Vector<String>();
        Pattern pattern = Pattern.compile("\\[\\w+\\]");
        Matcher match = pattern.matcher(this.eval);
        while(match.find(1)) {
            vars.add(match.group());
        }
        return vars;
    }

    /**
     * Solves the equation being passed to this object.
     * @return
     */
    public int solve()  {
        if(this.eval.equals("inf"))
            return 0;
        DoubleEvaluator calculator = new DoubleEvaluator();
        return (int) calculator.evaluate(this.getSolvableEquation());
    }

    /**
     * Any of the dice variables in this equation will be rolled and returned.<br />
     * It may be best to run and pass the results from <code>assignVariables(String)</code>
     * incase there is a an instance where you have a <i>[var]d6</i> expression in your
     * equation.
     * @param equations
     * @return
     */
    private String rollDice(String equations) {
        Pattern p = Pattern.compile("\\d*d\\d+");
        StringBuilder r = new StringBuilder(equations);
        Matcher m = p.matcher(r);
        while(m.find(0)) {
            int value = 0;
            String[] group = m.group().split("d");
            int sides = Integer.valueOf(group[1]).intValue();
            Die die = new Die(sides);
            if(group[0].equals("")) {
                value = die.roll();
            } else {
                int count = Integer.valueOf(group[0]).intValue();
                for(int i=1; i<=count; i++) {
                    value += die.roll();
                }
            }
            r.replace(m.start(), m.end(), String.valueOf(value));
        }
        return r.toString();
    }

    private String assignVariables(String equations) {
        StringBuilder builder = new StringBuilder(equations);
        Pattern pattern = Pattern.compile("\\[\\w+\\]");
        Matcher match = pattern.matcher(builder);
        while(match.find(1)) {
            String var = match.group();
            var = var.substring(1, var.length()-1);
            String value = String.valueOf(this.getVariableValue(var));
            builder.replace(match.start(), match.end(), value);
        }
        return builder.toString();
    }

    private double getVariableValue(String var) {
        double value = 0;
        VariableMap map = VariableMap.getVariableMap();
        String mod = map.getVariable(var);
        CharacterEvaluator ce = new CharacterEvaluator(this.compare);
        value = ce.getAbility(mod);
        if(value == 0)
            value = ce.getSkill(mod);
        return value;
    }
}